package sk.fiit.lulco.grafy;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Graph extends Item {

	private List<Vertex> vertices = new ArrayList<Vertex>();

	private List<Edge> edges = new ArrayList<Edge>();

	private int[][] matrix;

	private int chromaticNumber;

	private int bestColoring;

	private int[] initialColors;

	private Double fitness = null;

	private int timeToLive;

	private int width;

	private int height;

	/**
	 * construrtor - creates graph from file
	 * 
	 * @param String
	 *            filename
	 * @throws IOException
	 */
	public Graph(String filename) throws IOException {
		FileParser parser = new FileParser();
		ParsedData data = parser.parse(filename);
		this.fakeConstructor(data.getMatrix(), data.getChromaticNumber(), data.getBestColoring(),
				data.getInitialColors());
	}

	/**
	 * constructor - creates graph from matrix, chromatic number, best coloring
	 * and initial colors
	 * 
	 * @param int[][] matrix
	 * @param int chromaticNumber
	 * @params int bestColoring
	 * @param int[] initialColors
	 */
	public Graph(int[][] matrix, int chromaticNumber, int bestColoring, int[] initialColors) {
		this.fakeConstructor(matrix, chromaticNumber, bestColoring, initialColors);
	}

	/**
	 * constructor - creates copy of graph from Graph
	 * 
	 * @param Graph
	 *            graph
	 */
	public Graph(Graph graph) {
		int[][] matrix = graph.getMatrix();
		int chromaticNumber = graph.getChromaticNumber();
		int bestColoring = graph.getBestColoring();
		int[] initialColors = new int[graph.getVertices().size() + 1];
		for (Vertex vertex : graph.getVertices()) {
			initialColors[vertex.getId()] = vertex.getColor();
		}
		this.width = graph.getWidth();
		this.height = graph.getHeight();
		this.fakeConstructor(matrix, chromaticNumber, bestColoring, initialColors);
	}

	/**
	 * method for fake construction
	 * 
	 * @param int[][] matrix
	 * @param int chromaticNumber
	 * @param int[] initialColors
	 */
	private void fakeConstructor(int[][] matrix, int chromaticNumber, int bestColoring, int[] initialColors) {
		for (int i = 1; i < matrix.length; i++) {
			Vertex vertex = new Vertex(i);
			this.addVertex(vertex);
		}

		for (Vertex vertex : this.getVertices()) {
			int color = initialColors[vertex.getId()];
			if (color != 0) {
				vertex.setColor(color);
				// vertex.setFixed(true);
			}
		}

		this.createNeighbourhoods(matrix);
		this.matrix = matrix;
		this.chromaticNumber = chromaticNumber;
		this.bestColoring = bestColoring;
		this.initialColors = initialColors;
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	public void setWidth(int width) {
		this.width = width;
	}

	public void setHeight(int height) {
		this.height = height;
	}

	/**
	 * method for creating neighbourhoods between vertices
	 * 
	 * @param int[][] matrix
	 * @return void
	 */
	private void createNeighbourhoods(int[][] matrix) {
		for (Vertex vertex1 : this.vertices) {
			for (Vertex vertex2 : this.vertices) {
				if (matrix[vertex1.getId()][vertex2.getId()] == 1) {
					vertex1.addNeighbour(vertex2);
					this.addEdge(vertex1, vertex2);
				}
			}
		}
	}

	/**
	 * method for adding edge to graph
	 * 
	 * @param Vertex
	 *            vertex1
	 * @param Vertex
	 *            vertex2
	 * @return void
	 */
	private void addEdge(Vertex vertex1, Vertex vertex2) {
		this.edges.add(new Edge(vertex1, vertex2));
	}

	/**
	 * method for adding vertex to graph
	 * 
	 * @param Vertex
	 *            vertex
	 * @return void
	 */
	private void addVertex(Vertex vertex) {
		this.vertices.add(vertex);
	}

	/**
	 * getter for fitness
	 * 
	 * @notice cached
	 * @return double
	 */
	public double getFitness() {
		if (this.fitness != null) {
			return this.fitness;
		}
		double colorsCoefficient = 1.0;
		if (this.isWellColored() || this.getColors(null).size() > this.getBestColoring()) {
			colorsCoefficient = (double) this.getBestColoring() / (double) this.getColors(null).size();
		}

		List<Edge> conflictedEdges = this.getConflitedEdges();
		double fitness = colorsCoefficient
				* (1.0 - ((double) conflictedEdges.size() / (double) this.edges.size()))
				* (1.0 - ((double) this.getConflictedVertices(conflictedEdges).size() / (double) this.vertices
						.size()));
		this.fitness = fitness;
		return fitness;
	}

	/**
	 * method for clear fitness
	 * 
	 * @return void
	 */
	public void clearFitness() {
		this.fitness = null;
	}

	/**
	 * getter for conflicted vertices
	 * 
	 * @return List<Vertex>
	 */
	public List<Vertex> getConflictedVertices(List<Edge> conflictedEdges) {
		for (Vertex vertex : this.vertices) {
			vertex.clearConflicts();
		}

		List<Vertex> conflictedVertices = new ArrayList<Vertex>();
		if (conflictedEdges == null) {
			conflictedEdges = this.getConflitedEdges();
		}
		for (Edge edge : conflictedEdges) {
			Vertex vertex1 = edge.getVertex1();
			vertex1.addConflict();
			if (!conflictedVertices.contains(vertex1)) {
				conflictedVertices.add(vertex1);
			}
			Vertex vertex2 = edge.getVertex2();
			vertex2.addConflict();
			if (!conflictedVertices.contains(vertex2)) {
				conflictedVertices.add(vertex2);
			}
		}
		return conflictedVertices;
	}

	/**
	 * getter for conflicted edges
	 * 
	 * @return List<Edge>
	 */
	public List<Edge> getConflitedEdges() {
		List<Edge> conflictedEdges = new ArrayList<Edge>();
		for (Edge edge : this.edges) {
			if (edge.isConflicted() && !conflictedEdges.contains(edge)) {
				conflictedEdges.add(edge);
			}
		}
		return conflictedEdges;
	}

	/**
	 * getter for vertices
	 * 
	 * @return List<Vertex>
	 */
	public List<Vertex> getVertices() {
		return this.vertices;
	}

	/**
	 * checks if graph is colored
	 * 
	 * @return boolean
	 */
	public boolean isColored() {
		for (Vertex vertex : this.vertices) {
			if (vertex.getColor() == 0) {
				return false;
			}
		}
		return true;
	}

	/**
	 * checks if graph is well colored
	 * 
	 * @return boolean
	 */
	public boolean isWellColored() {
		if (!this.isColored()) {
			return false;
		}

		for (Edge edge : this.edges) {
			if (edge.isConflicted()) {
				return false;
			}
		}

		return true;
	}

	/**
	 * getter for minimal free color which is not in vertices
	 * 
	 * @param List
	 *            <Vertex> vertices
	 * @return integer
	 */
	public int getMinimalFreeColor(List<Vertex> vertices, List<Integer> tabuColors) {
		if (vertices == null) {
			vertices = this.vertices;
		}
		int minimalFreeColor = vertices.size() + 1;
		List<Integer> colors = this.getColors(vertices);
		if (tabuColors != null) {
			for (Integer color : tabuColors) {
				if (!colors.contains(color)) {
					colors.add(color);
				}
			}
		}
		for (int i = 1; i <= vertices.size() + 1; i++) {
			if (!colors.contains(i)) {
				minimalFreeColor = i;
				break;
			}
		}
		return minimalFreeColor;
	}

	/**
	 * getter for colors of vertices
	 * 
	 * @param List
	 *            <Vertex> vertices
	 * @return List<Vertex>
	 */
	public List<Integer> getColors(List<Vertex> vertices) {
		if (vertices == null) {
			vertices = this.vertices;
		}
		List<Integer> colors = new ArrayList<Integer>();
		for (Vertex vertex : vertices) {
			if (vertex.getColor() != 0 && !colors.contains(vertex.getColor())) {
				colors.add(vertex.getColor());
			}
		}
		return colors;
	}

	/**
	 * print colors of vertices
	 * 
	 * @return void
	 */
	public void printColors() {
		System.out.println("====================");
		for (Vertex vertex : this.getVertices()) {
			System.out.println(vertex.getId() + ": " + vertex.getColor());
		}
		System.out.println("====================");
	}

	/**
	 * getter for matrix
	 * 
	 * @return int[][]
	 */
	public int[][] getMatrix() {
		return this.matrix;
	}

	/**
	 * setter for matrix
	 * 
	 * @param int[][] matrix
	 * @return void
	 */
	public void setMatrix(int[][] matrix) {
		this.matrix = matrix;
	}

	/**
	 * getter for chromatic number
	 * 
	 * @return int
	 */
	public int getChromaticNumber() {
		return this.chromaticNumber;
	}

	/**
	 * setter for chromatic number
	 * 
	 * @param int chromaticNumber
	 * @return void
	 */
	public void setChromaticNumber(int chromaticNumber) {
		this.chromaticNumber = chromaticNumber;
	}

	/**
	 * getter for best coloring
	 * 
	 * @return int
	 */
	public int getBestColoring() {
		return this.bestColoring;
	}

	/**
	 * setter for best coloring
	 * 
	 * @param int bestColoring
	 * @return void
	 */
	public void setBestColoring(int bestColoring) {
		this.bestColoring = bestColoring;
	}

	/**
	 * getter for initial colors
	 * 
	 * @return int[]
	 */
	public int[] getInitialColors() {
		return this.initialColors;
	}

	/**
	 * getter for time to live
	 * 
	 * @return int
	 */
	public int getTimeToLive() {
		return this.timeToLive;
	}

	/**
	 * setter for time to live
	 * 
	 * @param int timeToLive
	 * @return void
	 */
	public void setTimeToLive(int timeToLive) {
		this.timeToLive = timeToLive;
	}
}